#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Copyright (c) 2011, minotavr_x86
#
#Разрешается повторное распространение и использование как в 
#виде исходного кода, так и в двоичной форме, с изменениями или 
#без, при соблюдении следующих условий:
#    -При повторном распространении исходного кода должно оставаться 
#      указанное выше уведомление об авторском праве, этот список условий и 
#      последующий отказ от гарантий.
#    -При повторном распространении двоичного кода должна сохраняться 
#      указанная выше информация об авторском праве, этот список условий и 
#      последующий отказ от гарантий в документации и/или в других материалах, 
#      поставляемых при распространении.
#    -Ни название <Организации>, ни имена ее сотрудников не могут быть 
#      использованы в качестве поддержки или продвижения продуктов, 
#      основанных на этом ПО без предварительного письменного разрешения.
#
#ЭТА ПРОГРАММА ПРЕДОСТАВЛЕНА ВЛАДЕЛЬЦАМИ АВТОРСКИХ ПРАВ И/ИЛИ ДРУГИМИ СТОРОНАМИ 
#«КАК ОНА ЕСТЬ» БЕЗ КАКОГО-ЛИБО ВИДА ГАРАНТИЙ, ВЫРАЖЕННЫХ ЯВНО ИЛИ ПОДРАЗУМЕВАЕМЫХ, 
#ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ, ПОДРАЗУМЕВАЕМЫЕ ГАРАНТИИ КОММЕРЧЕСКОЙ ЦЕННОСТИ И 
#ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ. НИ В КОЕМ СЛУЧАЕ, ЕСЛИ НЕ ТРЕБУЕТСЯ СООТВЕТСТВУЮЩИМ 
#ЗАКОНОМ, ИЛИ НЕ УСТАНОВЛЕНО В УСТНОЙ ФОРМЕ, НИ ОДИН ВЛАДЕЛЕЦ АВТОРСКИХ ПРАВ И НИ ОДНО 
#ДРУГОЕ ЛИЦО, КОТОРОЕ МОЖЕТ ИЗМЕНЯТЬ И/ИЛИ ПОВТОРНО РАСПРОСТРАНЯТЬ ПРОГРАММУ, КАК БЫЛО 
#СКАЗАНО ВЫШЕ, НЕ НЕСЁТ ОТВЕТСТВЕННОСТИ, ВКЛЮЧАЯ ЛЮБЫЕ ОБЩИЕ, СЛУЧАЙНЫЕ, СПЕЦИАЛЬНЫЕ 
#ИЛИ ПОСЛЕДОВАВШИЕ УБЫТКИ, ВСЛЕДСТВИЕ ИСПОЛЬЗОВАНИЯ ИЛИ НЕВОЗМОЖНОСТИ ИСПОЛЬЗОВАНИЯ 
#ПРОГРАММЫ (ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ПОТЕРЕЙ ДАННЫХ, ИЛИ ДАННЫМИ, СТАВШИМИ 
#НЕПРАВИЛЬНЫМИ, ИЛИ ПОТЕРЯМИ ПРИНЕСЕННЫМИ ИЗ-ЗА ВАС ИЛИ ТРЕТЬИХ ЛИЦ, 
#ИЛИ ОТКАЗОМ ПРОГРАММЫ РАБОТАТЬ СОВМЕСТНО С ДРУГИМИ ПРОГРАММАМИ), ДАЖЕ ЕСЛИ ТАКОЙ 
#ВЛАДЕЛЕЦ ИЛИ ДРУГОЕ ЛИЦО БЫЛИ ИЗВЕЩЕНЫ О ВОЗМОЖНОСТИ ТАКИХ УБЫТКОВ.

'''
Created on 03.03.2011

@author: minotavr_x86

'''

'''
----------TODO-------------
TODO: Реализация if
TODO: Склеивание двух документов
TODO: Экспорт в pdf
'''
import uno
import unohelper

class ReportGen():
    '''    
        Класс создания отчета
        host - хост где загружен OpenOffice
        port - порт
    '''
    def __init__(self, host = 'localhost', port = '8100'):                        
        localContext = uno.getComponentContext()
        oServiceManager = localContext.ServiceManager
        self.__dispatcher = oServiceManager.createInstanceWithContext( "com.sun.star.frame.DispatchHelper", localContext )
        resolver = localContext.ServiceManager.createInstanceWithContext(
                        "com.sun.star.bridge.UnoUrlResolver", localContext )  
        #TODO: Отлов прерываний        
        smgr = resolver.resolve( "uno:socket,host=%s,port=%s;urp;StarOffice.ServiceManager"%(host,port))        
        remoteContext = smgr.getPropertyValue( "DefaultContext" )
        self.__desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",remoteContext)
        
    def to_tuple(self, array, len_x, len_y = 1, default = u''):
        '''Приведение массива к кортежу,
        а всех элеметов к unicode.
        Добавление стандартного элемента в конец.
        array - массив
        len_x - количество переменных во вложенных массивах
        len_y - количество массивов
        default - чем заполнять недостоющие элементы            
        '''
        if len_y == 1 :
            #Если одна строка
            if isinstance(array[0], list):
                array = array[0]
            if len(array) == len_x:
                array = [array]
            elif len(array) < len_x:
                array = [array+[default]*(len_x - len(array))]
            elif len(array) > len_x:
                array = [array[:len_x]]
        elif len_x == 1:
            #Если столбец
            if isinstance(array[0], list):
                for el in array:
                    if not isinstance(el, list):
                        return -1
            else:
                temp = []
                for el in array:
                    temp.append([el])
                array = temp                   
            if len(array) == len_y:
                pass
            elif len(array) < len_y:
                array = array+[[default]]*(len_y - len(array))
            elif len(array) > len_y:
                array = array[:len_y]   
                    
        else:
            #Если матрица            
            for i in range(len_y):
                if i < len(array):                
                    if isinstance(array[i], list):                    
                        if len(array[i]) < len_x:
                            array[i] = array[i]+[default]*(len_x - len(array[i]))
                        elif len(array[i]) > len_x:
                            array[i] = array[i][:len_x]
                    else:
                        return -1
                else:
                    array.append([default]*len_x)             
        #Преобразование в кортеж и перекодировка в юникод
        for i in range(len(array)):            
            if isinstance(array[i], list):
                for j in range(len(array[i])):
                    if array[i][j] == None:
                        array[i][j] = '' 
                    elif isinstance(array[i][j], unicode):
                        array[i][j] = array[i][j].strip()
                    elif isinstance(array[i][j],str):
                        array[i][j] = unicode(array[i][j],'utf-8').strip()
                    else:
                        array[i][j] = unicode(array[i][j])                                   
                array[i] = tuple(array[i])            
        array = tuple(array)
        return array   
    
    def getDoc(self):
        '''Получить переменную документа
        '''
        return self.__doc  
    def getDispatcher(self):
        return self.__dispatcher 
    
    def open(self, template = 'test.odt'):
        '''Открытие файла
        '''
        template = uno.systemPathToFileUrl(template)
        self.__doc = self.__desktop.loadComponentFromURL(template, "_blank", 0, ())
        
    def render_tables(self, vars):
        '''Шаблонизатор таблиц.
        В шаблоне в начальной ячейки пишется {%mas <имя переменной массива>%} 
          в конечной {%end <имя переменной массива>%}.Пробелы в между служебным 
          словом и переменной можно не ставить.
        Данные вводятся в следующем формате: 
        [[<первая строка>],
        [<вторая строка>],
        ...]            
        При вставке строки или столбца разрешается использовать формат [<строка>]             
        '''        
        import re
        import copy
        count_tables = self.__doc.getTextTables().getCount()
        for id in range(count_tables) :  
            table = self.__doc.TextTables.getByIndex(id)           
            insert = {} # фомат записи {<имя массива>:[<x1>,<y1>,<x2>,<y2>]}
            mask_table = {}
            iter = {}
            cols_len = [] # количество ячеек в строке
            all_mas =re.compile(r'\{%(end|mas)\s{0,1}([a-zA-Z0-9_]*)%\}')
            for_re = re.compile(r'{%\s*for\s+([a-zA-Z0-9_]+)\s+in\s+([a-zA-Z0-9_]+)\s*%}')
            endfor_re = re.compile(r'{%\s*endfor\s+([a-zA-Z0-9_]+)\s*%}')
            var_re = re.compile(r'{{\s*([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\s*}}')
            iter_re = re.compile(r'{%\s*iter\s+([a-zA-Z0-9_]+)\s*%}')    
            for y in range(table.Rows.Count):
                x = 0
                while True:
                    try:                
                        table.getCellByPosition(x, y)                
                    except:
                        cols_len.append(x)
                        break
                    cell = table.getCellByPosition(x, y).getString()
                    pers = for_re.findall(cell)            
                    if pers:
                        if pers[0][1] in mask_table:
                            mask_table[pers[0][1]].update({'start':[x,y],'el':pers[0][0]})
                        else:
                            mask_table[pers[0][1]] = {'start':[x,y],'el':pers[0][0]} 
                    pers = endfor_re.findall(cell)
                    if pers:
                        if pers[0] in mask_table:
                            mask_table[pers[0]].update({'stop':[x,y]})
                        else:
                            mask_table[pers[0]] = {'stop':[x,y]}
                    pers = var_re.findall(cell)
                    if pers:                                                           
                        for key in mask_table:
                            if 'el' in mask_table[key] and pers[0][0] == mask_table[key]['el']:
                                if 'vars' in mask_table[key]:
                                    mask_table[key]['vars'].update({pers[0][1]:[x,y]}) 
                                else:
                                    mask_table[key]['vars'] = {pers[0][1]:[x,y]}
                                break
                    pers = all_mas.findall(unicode(cell))
                    if pers:                       
                        if pers[0][0] == u'mas':  
                            insert[pers[0][1]] = [x, y]
                        elif pers[0][1] in insert:
                            insert[pers[0][1]] += [x, y]
                    #TODO: Переделать что бы проходили через весь документ 
                    pers = iter_re.findall(cell) 
                    if pers:                
                        if pers[0] in iter:                    
                            iter[pers[0]] += 1
                        else:
                            iter[pers[0]] = 0
                        if len(vars[pers[0]]) > iter[pers[0]] and pers[0] in vars:                     
                            table.getCellByPosition(x,y).setString(vars[pers[0]][iter[pers[0]]])
                        else:
                            table.getCellByPosition(x,y).setString(u'')                                    
                    x += 1                              
            
                                
            for key in insert:
                '''вставка массивов по маске
                '''
                if len(insert[key]) == 4:
                    insert[key][2] = cols_len[insert[key][1]] - (cols_len[insert[key][3]] - insert[key][2])
                    len_x = insert[key][2]-insert[key][0]+1
                    len_y = insert[key][3]-insert[key][1]+1
                    if key in vars:
                        mas = copy.copy(vars[key])
                    else:
                        mas = []
                        for i in range(len_y): 
                            mas += [u' ']*len_y
                    mas = self.to_tuple(mas, len_x, len_y)
                    if mas != -1:                       
                        start_x = insert[key][0]                        
                        i = 0
                        for y in range(insert[key][1], insert[key][3]+1):                            
                            j = 0  
                            for x in range(start_x, start_x + len_x):                                                                        
                                table.getCellByPosition(x,y).setString(mas[i][j])
                                j += 1                            
                            if y < insert[key][3]:
                                start_x = cols_len[y + 1] - (cols_len[y] - start_x)                                
                            i +=1
            for cycle in mask_table:
                ''' Вставка цыклов                    
                '''
                if not ('start' in mask_table[cycle] and 'stop' in mask_table[cycle]):
                    continue                
                if cycle in vars and vars[cycle] and isinstance(vars[cycle],list):            
                    if mask_table[cycle]['start'][1] == mask_table[cycle]['stop'][1]:
                        #Цыкл вниз
                        y = mask_table[cycle]['start'][1]
                        if len(vars[cycle]) > table.Rows.Count - y:
                            #вставка не достающих строк
                            add_len = (y + len(vars[cycle]))- table.Rows.Count
                            table.getRows().insertByIndex(table.Rows.Count, add_len)                
                            cols_len += [cols_len[len(cols_len)-1]]*add_len                            
                        for stroka in vars[cycle]:
                            if isinstance(stroka,dict):                                                                
                                for var in mask_table[cycle]['vars']:  
                                    x = mask_table[cycle]['vars'][var][0]
                                    if var in stroka:                       
                                        rezult = stroka[var]
                                    else: 
                                        rezult = u''
                                    table.getCellByPosition(x,y).setString(rezult) 
                                    if y+1 < len(vars[cycle]):
                                        mask_table[cycle]['vars'][var][0] = x + (cols_len[y+1] - cols_len[y])
                            
                            y += 1
                    elif mask_table[cycle]['start'][0] == mask_table[cycle]['stop'][0]:
                        #Цыкл в право
                        #TODO: Реализовать вставку вправо
                        pass
                else:
                    #TODO: Нужноли это или лучше показывать неправильный шаблон
                    table.getCellByPosition(*mask_table[cycle]['start']).setString(u'') 
                    table.getCellByPosition(*mask_table[cycle]['stop']).setString(u'')
                    for key in mask_table[cycle]['vars']:
                        table.getCellByPosition(*mask_table[cycle]['vars'][key]).setString(u'')  
 
    def render(self, vars, visible = True):
        '''Обработка документа шаблонизатором
        vars - словарь переменных 
        '''            
        import string
        import re
        search = self.__doc.createSearchDescriptor()
        search.SearchRegularExpression = True
        find = ur"{{ *[a-zA-Z0-9_]+ *}}"    
        search.SearchString = find   
        found = self.__doc.findFirst(search)     
        if found:               
            while found:
                name = re.compile('[a-zA-Z0-9_]+')
                name = name.findall(found.String)[0]
                if name in vars:                    
                    if vars[name] == None:
                        var = '' 
                    elif isinstance(vars[name], unicode):
                        var = vars[name].strip()
                    elif isinstance(vars[name],str):
                        var = unicode(vars[name],'utf-8').strip()
                    else:
                        var = unicode(vars[name])
                                        
                    if found.String[2] == ' ' or found.String[-3] == ' ':
                        l = len(found.String)
                        l_ =  l - len(var)
                        if l_ < 0:
                            replace = var[:l-1] + u'~'                            
                        elif l_ == 0:
                            replace = var
                        elif found.String[2] == ' ' and found.String[-3] == ' ':                            
                            l_start = l_/2 + l_% 2
                            l_finish = l_/2
                            replace = ' '*l_start + var + ' '*l_finish                        
                        elif found.String[2] == ' ':                                                                        
                            replace = ' '*l_ + var                        
                        elif found.String[-3] == ' ':                                           
                            replace = var + ' '*l_                        
                    else:
                        replace = var            
                else:
                    if visible:
                        replace = found.String
                    else:
                        replace = u' '*len(found.String)
                found.String = string.replace( found.String, found.String,replace)
                found = self.__doc.findNext( found.End, search)     
    
            
    def save(self, result_file):
        '''Сохранение файла
        '''
        outputUrl = uno.systemPathToFileUrl(result_file)
        self.__doc.storeAsURL(outputUrl, ())
                
    def close(self):
        '''Закрытие файла
        '''
        self.__doc.dispose()
    
    def create_one_document(self, template, vars, result_file):
        self.open(template)
        self.render_tables(vars)
        self.render(vars)              
        self.save(result_file)
        self.close()